package com.otom.bcel;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import com.otom.bcel.annotations.FieldExclude;
import com.otom.bcel.annotations.MapClassTo;
import com.otom.bcel.annotations.MapFieldList;
import com.otom.bcel.annotations.MapTo;

public class ClassInfo extends BaseObject {
	private Class<?> source;
	private Class<?> destination;
	private boolean mapWildCard;
	private boolean biDirectional;
	private List<ClassMapper> dependentClasses = new ArrayList<ClassMapper>();//new LinkedHashMap<Class<?>, Class<?>>();
	private List<FieldMapping> fieldMappingList = new ArrayList<FieldMapping>();

	public List<ClassMapper> getDependentClasses() {//Map<Class<?>, Class<?>>
		return dependentClasses;
	}

	public Class<?> getSource() {
		return source;
	}

	public boolean isBiDirectional() {
		return biDirectional;
	}

	public void setBiDirectional(boolean biDirectional) {
		this.biDirectional = biDirectional;
	}

	public void setSource(Class<?> source) {
		this.source = source;
	}

	public Class<?> getDestination() {
		return destination;
	}

	public void setDestination(Class<?> destination) {
		this.destination = destination;
	}

	public boolean isMapWildCard() {
		return mapWildCard;
	}

	public void setMapWildCard(boolean mapWildCard) {
		this.mapWildCard = mapWildCard;
	}

	public List<FieldMapping> getFieldMappingList() {
		return fieldMappingList;
	}

	public void setFieldMappingList(List<FieldMapping> fieldMappingList) {
		this.fieldMappingList = fieldMappingList;
	}

	public void initFromAnnotations(Class<?> source, MapClassTo classMapper) {
		this.source = source;
		//MapClassTo classMapper = source.getAnnotation(MapClassTo.class);
		mapWildCard = classMapper.wildcard();
		destination = classMapper.value();
		biDirectional = classMapper.biDirectional();

		initFieldsFromAnnotations(source);
	}

	public void initFieldsFromAnnotations(Class<?> source) {
		for (Field field : FieldHelper.getFields(source)) {
			FieldMapping fieldMapping = new FieldMapping();

			if (hadMappedField(field)) {
				fieldMapping.initFromAnnotations(this, field);				
				fieldMappingList.add(fieldMapping);
			}
		}
		
		FieldMappingValidator fieldMappingValidator = new FieldMappingValidator();
		fieldMappingValidator.validate(fieldMappingList, biDirectional);
	}

	public ClassInfo getBiDirectionalClassInfo() {
		ClassInfo biClassInfo = new ClassInfo();
		biClassInfo.setSource(destination);
		biClassInfo.setDestination(source);

		for (FieldMapping fieldMapping : getFieldMappingList()) {
			FieldMapping biFieldMapping = new FieldMapping();
			biFieldMapping.setMappedClass(fieldMapping.isMappedClass());
			biFieldMapping.setPostCallBack(fieldMapping.getPostCallBack());
			biFieldMapping.setSource(fieldMapping.getDestination());
			biFieldMapping.setDestination(fieldMapping.getSource());
			biClassInfo.getFieldMappingList().add(biFieldMapping);
		}

		return biClassInfo;
	}

	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof ClassInfo)) {
			return false;
		}
		ClassInfo other = (ClassInfo) obj;
		return (this.source == (other.source))
				&& (this.destination == other.destination);
	}

	@Override
	public int hashCode() {
		return (source + "_" + destination).hashCode();
	}

	public boolean hadMappedField(Field field) {
		boolean valid = false;
		
		if (field.getAnnotation(FieldExclude.class) != null) {
			return false;
		}
		
		MapTo mapTo = field.getAnnotation(MapTo.class);
		MapFieldList mapFieldList = field.getAnnotation(MapFieldList.class);
		if (mapTo != null && (mapTo.forClass() == void.class 
				|| mapTo.forClass() == destination)) {
			valid = true;
		}
		else if (mapFieldList != null) {
			for (MapTo fmapTo : mapFieldList.value()) {
				if (fmapTo != null
						&& (fmapTo.forClass() == void.class || fmapTo
								.forClass() == destination)) {
					valid = true;
					break;
				}
			}
		}
		else if (mapWildCard) {
			for (Field desninationField : destination.getDeclaredFields()) {				
				if (desninationField.getName().equals(field.getName())) {
					valid = true;
					break;
				}
			}
		}
		
		if (field.getName().startsWith("this$")) {
			valid = false;
		}
		
		return valid;
	}
}
